package cn.zyx.user.service.impl;

import cn.zyx.basic.util.*;
import cn.zyx.user.constant.UserConstant;
import cn.zyx.user.constant.WxConstants;
import cn.zyx.user.domain.LoginInfo;
import cn.zyx.user.domain.User;
import cn.zyx.user.domain.WxUser;
import cn.zyx.user.dto.LoginDto;
import cn.zyx.user.dto.UserDto;
import cn.zyx.user.mapper.LoginInfoMapper;
import cn.zyx.user.mapper.UserMapper;
import cn.zyx.user.mapper.WxUserMapper;
import cn.zyx.user.service.ILoginService;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 统一登录业务层
 */
@Service
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public class ILoginServiceImpl implements ILoginService {

    @Autowired
    private LoginInfoMapper loginInfoMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private WxUserMapper wxUserMapper;
    @Autowired
    private UserMapper userMapper;

    /**
     * 账号密码登录
     *
     * @param loginDto
     * @return
     */
    @Override
    public AjaxResult account(LoginDto loginDto) {
        // 空校验 -- 校验LoginDto中的所有字段
        if (StringUtils.isEmpty(loginDto.getUsername()) ||
                StringUtils.isEmpty(loginDto.getPassword()) ||
                StringUtils.isEmpty(loginDto.getType())) {
            return AjaxResult.me().setMessage("账号和密码不能为空!");
        }
        // 判断用户是否存在 -- 查询LoginInfo表判断
        LoginInfo loginInfo = loginInfoMapper.loadByLoginDto(loginDto);
        if (loginInfo == null) {
            return AjaxResult.me().setMessage("用户不存在!");
        }
        // 判断账号是否被禁用
        if (loginInfo.getDisable() != 1) {
            return AjaxResult.me().setMessage("该账号已被禁用!");
        }
        // 判断密码是否正确
        // 获取数据库盐值
        String salt = loginInfo.getSalt();
        // 获取数据库密码
        String password = loginInfo.getPassword();
        // 把数据库盐值和用户传过来的密码加密
        String md5Pwd = MD5Utils.encrypByMd5(loginDto.getPassword() + salt);
        // 如果密码不相等，抛错
        if (!password.equals(md5Pwd)) {
            return AjaxResult.me().setMessage("账号或密码错误!");
        }
        // 如果密码相等，登录成功，把登录成功后的信息存在redis，并且封装返回值
        // 这里的redis，key就是前端需要储存的token
        // 生成token
        String token = UUID.randomUUID().toString();
        // 把登录成功后的信息存在redis -- redis中存储对象，必须要实现序列化接口 -- 后端存储
        redisTemplate.opsForValue().set(token, loginInfo, 30, TimeUnit.MINUTES);
        // 定义一个hashMap,封装返回值 -- 前端储存
        Map<String, Object> map = new HashMap<>();
        map.put("token", token);
        // 我们不能将登录人的账号和密码传给前端，应该过滤掉
        loginInfo.setUsername(null);
        loginInfo.setPassword(null);
        map.put("loginInfo", loginInfo);
        return AjaxResult.me().setResultObj(map);
    }

    /**
     * 微信扫码登录
     *
     * @param params
     * @return
     */
    @Override
    public AjaxResult weChat(Map<String, String> params) {
        // 获取到前台传过来的参数
        String code = params.get("code");
        String binderUrl = params.get("binderUrl");
        // 参数的空校验
        if (StringUtils.isEmpty(code) || StringUtils.isEmpty(binderUrl)) {
            return AjaxResult.me().setMessage("必要参数不能为空!");
        }
        // 发送http请求，通过code去微信平台拿token
        // 获取拿token的地址 --> replace替换内容
        String accessTokenUrl = WxConstants.GET_ACK_URL
                .replace("APPID", WxConstants.APPID)
                .replace("SECRET", WxConstants.SECRET)
                .replace("CODE", code);
        // 利用工具类，发送http请求 -- 返回一个json字符串
        String strObj = HttpClientUtils.httpGet(accessTokenUrl);
        // 利用阿里提供的工具类把json字符串转换成json对象
        JSONObject object = JSONObject.parseObject(strObj);
        // json对象取值 -- 这里它不知道是什么类型，需要强转
        String accessToken = (String) object.get("access_token");
        String openId = (String) object.get("openid");
        // 判断是否绑定了微信用户 --> 通过openId去查询微信用户信息，如果查询到了并且userId有值，表示绑定了
        WxUser wxUser = wxUserMapper.loadByOpenId(openId);
        // 都不为空表示绑定微信用户，直接免密登录
        if (wxUser != null && wxUser.getUser_id() != null) {
            // 通过用户的id查询到登录人的信息
            LoginInfo loginInfo = loginInfoMapper.loadByUserId(wxUser.getUser_id());
            // 这里的redis，key就是前端需要储存的token
            // 生成token
            String token = UUID.randomUUID().toString();
            // 把登录成功后的信息存在redis -- redis中存储对象，必须要实现序列化接口 -- 后端存储
            redisTemplate.opsForValue().set(token, loginInfo, 30, TimeUnit.MINUTES);
            // 定义一个hashMap,封装返回值 -- 前端储存
            Map<String, Object> map = new HashMap<>();
            map.put("token", token);
            // 我们不能将登录人的账号的密码传给前端，应该过滤掉
            loginInfo.setUsername(null);
            loginInfo.setPassword(null);
            map.put("loginInfo", loginInfo);
            return AjaxResult.me().setResultObj(map);
        } else {
            // 如果没有查询到，表示没有绑定微信用户，应该跳转到绑定页面
            // 获取到绑定页面的路径,并把accessToken和openId返回给前端，通过这两个参数去微信查询微信用户的基本信息
            String resultUrl = binderUrl + "?accessToken=" + accessToken + "&openId=" + openId;
            // 把错误信息和绑定页面的路径返回给前端
            return AjaxResult.me().setSuccess(false).setResultObj(resultUrl);
        }
    }

    /**
     * 微信扫码绑定微信用户
     * @param params
     * @return
     */
    @Override
    @Transactional
    public AjaxResult binder(Map<String, String> params) {
        // 参数空校验
        String phone = params.get("phone");
        String verifyCode = params.get("verifyCode");
        String accessToken = params.get("accessToken");
        String openId = params.get("openId");
        if (StringUtils.isEmpty(phone) ||
                StringUtils.isEmpty(verifyCode) ||
                StringUtils.isEmpty(accessToken) ||
                StringUtils.isEmpty(openId)) {
            return AjaxResult.me().setMessage("必要参数不能为空!");
        }
        // 验证码是否存在
        Object codeObj = redisTemplate.opsForValue().get(UserConstant.USER_BINDER_CODE + ":" + phone);
        // 验证码是否已经过期
        if (codeObj == null) {
            return AjaxResult.me().setMessage("验证码已过期,请重新发送!");
        }
        // 截取验证码
        String code = ((String) codeObj).split(":")[0];
        // 验证码是否正确
        if (!verifyCode.equalsIgnoreCase(code)) {
            return AjaxResult.me().setMessage("验证码错误!");
        }
        // 通过accessToken和openId去查询微信用户
        // 获取到微信个人信息的地址 --> replace替换内容
        String wxUserUrl = WxConstants.GET_USER_URL
                .replace("ACCESS_TOKEN", accessToken)
                .replace("OPENID", openId);
        // 利用工具类，发送http请求，拿到微信用户的个人信息 -- 返回一个json字符串
        String strObj = HttpClientUtils.httpGet(wxUserUrl);

        // 通过用户输入的手机查询用户是否存在
        UserDto userDto = new UserDto();
        userDto.setPhone(phone);
        userDto.setType(1);
        LoginInfo loginInfo = loginInfoMapper.loadByDto(userDto);

        User user = null;
        // 如果存在，说明手机号已经注册过，直接绑定微信用户，免密登录
        if (loginInfo != null) {
            // 通过用户信息的id查询到用户
            user = userMapper.loadByLoginInfoId(loginInfo.getId());
            // wxUser.setUser_id(user.getId());
            // wxUserMapper.save(wxUser);
        } else {
            // 如果不存在，需要创建loginInfo信息，User信息，WxUser信息，并且User和WxUser要绑定，再免密登录
            loginInfo = createLoginInfo(phone);
            user = loginInfo2User(loginInfo);
            loginInfoMapper.save(loginInfo);
            user.setInfo(loginInfo);
            userMapper.save(user);
        }

        // 调用方法，把查询到的微信的个人信息和用户id转换成WxUser对象，并且绑定
        WxUser wxUser = user2WxUser(strObj, user);
        wxUserMapper.save(wxUser);

        // 免密登录
        // 生成token
        String token = UUID.randomUUID().toString();
        // 把登录成功后的信息存在redis -- redis中存储对象，必须要实现序列化接口 -- 后端存储
        redisTemplate.opsForValue().set(token, loginInfo, 30, TimeUnit.MINUTES);
        // 定义一个hashMap,封装返回值 -- 前端储存
        Map<String, Object> map = new HashMap<>();
        map.put("token", token);
        // 我们不能将登录人的账号的密码传给前端，应该过滤掉
        loginInfo.setUsername(null);
        loginInfo.setPassword(null);
        map.put("loginInfo", loginInfo);
        return AjaxResult.me().setResultObj(map);
    }

    /**
     * 把查询到的微信的个人信息赋值给WxUser对象,并且绑定用户
     * @param wxUserStr
     * @param user
     * @return
     */
    private WxUser user2WxUser(String wxUserStr, User user) {
        //装换微信用户信息
        JSONObject jsonObject = JSONObject.parseObject(wxUserStr);
        WxUser wxUser = new WxUser();
        wxUser.setUser_id(user.getId());//绑定平台用户
        wxUser.setOpenid(jsonObject.getString("openid"));
        wxUser.setNickname(jsonObject.getString("nickname"));
        wxUser.setSex(jsonObject.getInteger("sex"));
        wxUser.setAddress(null);
        wxUser.setHeadimgurl(jsonObject.getString("headimgurl"));
        wxUser.setUnionid(jsonObject.getString("unionid"));
        return wxUser;
    }

    /**
     * 把用户信息拷贝给用户
     * @param loginInfo
     * @return
     */
    private User loginInfo2User(LoginInfo loginInfo) {
        User user = new User();
        BeanUtils.copyProperties(loginInfo, user);//根据同名原则拷贝
        return user;
    }

    /**
     * 通过Phone创建info
     * @param phone
     * @return
     */
    private LoginInfo createLoginInfo(String phone) {
        LoginInfo loginInfo = new LoginInfo();
        loginInfo.setUsername(phone);
        loginInfo.setPhone(phone);
        loginInfo.setType(1);
        //设置一个默认密码
        String salt = StrUtils.getComplexRandomString(32);
        // 告诉用户的默认密码
        String pwd = StrUtils.getComplexRandomString(6);
        // 发送短信，告诉用户我们随机生成的密码 --> 使用工具类掉短信接口即可
        // SmsUtils.sendSms(phone,  "您在平台的默认密码是："+pwd);
        // 这里直接打印，发短信要钱
        System.out.println("您在平台的默认密码是："+pwd);
        // 储存在数据库的加密密码
        String md5Pwd = MD5Utils.encrypByMd5(pwd + salt);
        loginInfo.setSalt(salt);
        loginInfo.setPassword(md5Pwd);
        return loginInfo;
    }
}
